package javax.generic.swing;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

import javax.generic.persistance.AbstractPersistableEntity;

import org.apache.log4j.Logger;
import org.springframework.util.StringUtils;

public final class ClassUtils {

	private static final Logger LOG = Logger.getLogger(ClassUtils.class);

	public static final Class<?> findExactClass(Class<?> clazz, String methodName) {

		Method[] declaredMethods = clazz.getDeclaredMethods();
		for (Method declaredMethod : declaredMethods) {
			if (declaredMethod.getName().equals(methodName)) {
				return clazz;
			}
		}
		Class<?> superclass = clazz.getSuperclass();
		if (superclass.getSimpleName().equals("Object")) {
			return null;
		}
		return findExactClass(superclass, methodName);
	}

	public static List<FieldInfo> extractVisualFields(Class<?> clazz, boolean complex) {
		LOG.info("extracting fields for " + clazz.getName());
		List<FieldInfo> visualFields = new ArrayList<FieldInfo>();
		List<String> path = new ArrayList<String>();
		extractVisualFields(clazz, clazz, clazz, visualFields, path, false);
		return visualFields;
	}

	public static void extractVisualFields(Class<?> clazz, Class<?> parentClazz, Class<?> sourceClazz, List<FieldInfo> visualFields, List<String> path, boolean complex) {
		LOG.debug(String.format("extractVisualFields %s %s %s complex=%b", clazz.getSimpleName(), parentClazz.getSimpleName(), sourceClazz.getSimpleName(), complex));
		Class<?> superclass = clazz.getSuperclass();
		if (!superclass.getSimpleName().equals("Object")) {
			extractVisualFields(superclass, superclass, sourceClazz, visualFields, path, complex);
		}

		Field[] fields = clazz.getDeclaredFields();

		for (Field field : fields) {
			if (!Modifier.isStatic(field.getModifiers())) {
				VisualField visualField = getFieldAnotation(field);
				if (visualField != null) {

					if (visualField.type().equals(VisualFieldType.COMPLEX)) {
						LOG.debug("found complex visual annotation for field " + field.getName());
						List<String> newPath = new ArrayList<String>();
						newPath.addAll(path);
						newPath.add(field.getName());
						extractVisualFields(field.getType(), parentClazz, parentClazz, visualFields, newPath, true);
					} else {
						if (complex) {
							visualFields.add(new FieldInfo(visualField, field, sourceClazz, path, complex, 0));
						} else {
							visualFields.add(new FieldInfo(visualField, field, parentClazz, path, complex, 0));
						}

					}
				}
			}
		}

	}

	public static VisualField getFieldAnotation(Field field) {
		LOG.debug("reading found visual field annotation for field " + field.getName());
		Annotation[] annotations = field.getDeclaredAnnotations();
		for (Annotation annotation : annotations) {
			if (annotation instanceof VisualField) {
				LOG.debug("found visual field annotation " + annotation);
				return (VisualField)annotation;
			}
		}
		LOG.debug("visual field not faound forfield " + field.getName());
		return null;
	}

	/**
	 * Fetches value from model.
	 * 
	 * @param fieldInfo
	 * @return
	 */
	public static Object getValue(final FieldInfo fieldInfo, Object object) {
		try {
			String methodName = "get" + StringUtils.capitalize(fieldInfo.getField().getName());

			Object classRef = object;
			Class<?> classRefType = fieldInfo.getParent();
			List<String> path = fieldInfo.getPath();

			for (String string : path) {
				Method method = classRefType.getDeclaredMethod("get" + StringUtils.capitalize(string));
				classRef = method.invoke(object);
				if (classRef == null) {
					throw new IllegalStateException();
				}
				classRefType = ClassUtils.findExactClass(classRef.getClass(), methodName);
			}

			Method method = classRefType.getDeclaredMethod(methodName);
			return method.invoke(classRef);
		} catch (Exception e) {
			e.printStackTrace();
			throw new IllegalStateException(e);
		}
	}

	public static Object setValue(Object object, FieldInfo fieldInfo, String value) throws Exception {
		Object newValue = null;
		String methodName = "get" + StringUtils.capitalize(fieldInfo.getField().getName());

		Class<?> type = fieldInfo.getField().getType();
		Object classRef = object;
		Class<?> classRefType = fieldInfo.getParent();
		List<String> path = fieldInfo.getPath();

		for (String string : path) {
			Method method = classRefType.getDeclaredMethod("get" + StringUtils.capitalize(string));
			classRef = method.invoke(object);

			if (classRef == null) {
				LOG.error("null value found for:" + fieldInfo);
				throw new IllegalStateException();
			}

			classRefType = ClassUtils.findExactClass(classRef.getClass(), methodName);
		}

		Method method = classRefType.getDeclaredMethod("set" + StringUtils.capitalize(fieldInfo.getField().getName()), type);
		if (value.isEmpty()) {
			newValue = null;
		} else if (type.equals(String.class)) {
			newValue = value;
		} else {
			Class<?> clazz = Class.forName(fieldInfo.getField().getType().getName());
			Method mainMethod = clazz.getMethod("valueOf", new Class[] { String.class });
			newValue = mainMethod.invoke(null, new Object[] { value });
		}

		method.invoke(classRef, newValue);
		return newValue;
	}

	public static void add(AbstractPersistableEntity fromDb, AbstractPersistableEntity object) {
		List<FieldInfo> extractVisualFields = ClassUtils.extractVisualFields(fromDb.getClass(),
			false);

		for (FieldInfo fieldInfo : extractVisualFields) {
			Object value = ClassUtils.getValue(fieldInfo, fromDb);
			Object valueToAdd = ClassUtils.getValue(fieldInfo, object);
			if (!fieldInfo.getField().getName().equals("id") && !fieldInfo.getFieldAnotation().label().equals("Godina") && value instanceof Number) {

				if (valueToAdd != null) {
					Number n = null;
					if (value instanceof Integer) {
						Integer n1 = (Integer)value;
						Integer n2 = (Integer)valueToAdd;
						n = n1 + n2;
					}
					if (value instanceof Long) {
						Long n1 = (Long)value;
						Long n2 = (Long)valueToAdd;
						n = n1 + n2;
					}
					if (value instanceof Double) {
						Double n1 = (Double)value;
						Double n2 = (Double)valueToAdd;
						n = n1 + n2;
					}
					if (value instanceof Float) {
						Float n1 = (Float)value;
						Float n2 = (Float)valueToAdd;
						n = n1 + n2;
					}

					try {
						ClassUtils.setValue(fromDb, fieldInfo, n.toString());
					} catch (Exception e) {}

				}
			}
		}
	}

	public static void substract(AbstractPersistableEntity fromDb, AbstractPersistableEntity object) {
		List<FieldInfo> extractVisualFields = ClassUtils.extractVisualFields(fromDb.getClass(),
			false);

		for (FieldInfo fieldInfo : extractVisualFields) {
			Object value = ClassUtils.getValue(fieldInfo, fromDb);
			Object valueToAdd = ClassUtils.getValue(fieldInfo, object);
			if (!fieldInfo.getField().getName().equals("id") && !fieldInfo.getFieldAnotation().label().equals("Godina") && value instanceof Number) {

				if (valueToAdd != null) {
					Number n = null;
					if (value instanceof Integer) {
						Integer n1 = (Integer)value;
						Integer n2 = (Integer)valueToAdd;
						n = n1 - n2;
					}
					if (value instanceof Long) {
						Long n1 = (Long)value;
						Long n2 = (Long)valueToAdd;
						n = n1 - n2;
					}
					if (value instanceof Double) {
						Double n1 = (Double)value;
						Double n2 = (Double)valueToAdd;
						n = n1 - n2;
					}
					if (value instanceof Float) {
						Float n1 = (Float)value;
						Float n2 = (Float)valueToAdd;
						n = n1 - n2;
					}

					try {
						ClassUtils.setValue(fromDb, fieldInfo, n.toString());
					} catch (Exception e) {}

				}
			}
		}
	}
}
